/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2013 Live Networks, Inc.  All rights reserved.
// Common routines used by both RTSP clients and servers
// Implementation

#include "RTSPCommon.hh"
#include "Locale.hh"
#include <string.h>
#include <stdio.h>
#include <ctype.h> // for "isxdigit()
#include <time.h> // for "strftime()" and "gmtime()"

#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
#else
#include <signal.h>
#define USE_SIGNALS 1
#endif

static void decodeURL(char* url) {
	// Replace (in place) any %<hex><hex> sequences with the appropriate 8-bit character.
	char* cursor = url;
	while (*cursor) {
		if ((cursor[0] == '%') &&
			cursor[1] && isxdigit(cursor[1]) &&
			cursor[2] && isxdigit(cursor[2])) {
				// We saw a % followed by 2 hex digits, so we copy the literal hex value into the URL, then advance the cursor past it:
				char hex[3];
				hex[0] = cursor[1];
				hex[1] = cursor[2];
				hex[2] = '\0';
				*url++ = (char)strtol(hex, NULL, 16);
				cursor += 3;
		} else {
			// Common case: This is a normal character or a bogus % expression, so just copy it
			*url++ = *cursor++;
		}
	}

	*url = '\0';
}

Boolean parseRTSPRequestString(char const* reqStr,
							   unsigned reqStrSize,
							   char* resultCmdName,
							   unsigned resultCmdNameMaxSize,
							   char* resultURLPreSuffix,
							   unsigned resultURLPreSuffixMaxSize,
							   char* resultURLSuffix,
							   unsigned resultURLSuffixMaxSize,
							   char* resultCSeq,
							   unsigned resultCSeqMaxSize,
							   char* resultSessionIdStr,
							   unsigned resultSessionIdStrMaxSize,
							   unsigned& contentLength) {
								   // This parser is currently rather dumb; it should be made smarter #####

								   // Read everything up to the first space as the command name:
								   Boolean parseSucceeded = False;
								   unsigned i;
								   for (i = 0; i < resultCmdNameMaxSize-1 && i < reqStrSize; ++i) {
									   char c = reqStr[i];
									   if (c == ' ' || c == '\t') {
										   parseSucceeded = True;
										   break;
									   }

									   resultCmdName[i] = c;
								   }
								   resultCmdName[i] = '\0';
								   if (!parseSucceeded) return False;

								   // Skip over the prefix of any "rtsp://" or "rtsp:/" URL that follows:
								   unsigned j = i+1;
								   while (j < reqStrSize && (reqStr[j] == ' ' || reqStr[j] == '\t')) ++j; // skip over any additional white space
								   for (; (int)j < (int)(reqStrSize-8); ++j) {
									   if ((reqStr[j] == 'r' || reqStr[j] == 'R')
										   && (reqStr[j+1] == 't' || reqStr[j+1] == 'T')
										   && (reqStr[j+2] == 's' || reqStr[j+2] == 'S')
										   && (reqStr[j+3] == 'p' || reqStr[j+3] == 'P')
										   && reqStr[j+4] == ':' && reqStr[j+5] == '/') {
											   j += 6;
											   if (reqStr[j] == '/') {
												   // This is a "rtsp://" URL; skip over the host:port part that follows:
												   ++j;
												   while (j < reqStrSize && reqStr[j] != '/' && reqStr[j] != ' ') ++j;
											   } else {
												   // This is a "rtsp:/" URL; back up to the "/":
												   --j;
											   }
											   i = j;
											   break;
									   }
								   }

								   // Look for the URL suffix (before the following "RTSP/"):
								   parseSucceeded = False;
								   for (unsigned k = i+1; (int)k < (int)(reqStrSize-5); ++k) {
									   if (reqStr[k] == 'R' && reqStr[k+1] == 'T' &&
										   reqStr[k+2] == 'S' && reqStr[k+3] == 'P' && reqStr[k+4] == '/') {
											   while (--k >= i && reqStr[k] == ' ') {} // go back over all spaces before "RTSP/"
											   unsigned k1 = k;
											   while (k1 > i && reqStr[k1] != '/') --k1;

											   // ASSERT: At this point
											   //   i: first space or slash after "host" or "host:port"
											   //   k: last non-space before "RTSP/"
											   //   k1: last slash in the range [i,k]

											   // The URL suffix comes from [k1+1,k]
											   // Copy "resultURLSuffix":
											   unsigned n = 0, k2 = k1+1;
											   if (i <= k) { // There's a slash after "host" or "host:port"
												   if (k - k1 + 1 > resultURLSuffixMaxSize) return False; // there's no room
												   while (k2 <= k) resultURLSuffix[n++] = reqStr[k2++];
											   }
											   resultURLSuffix[n] = '\0';

											   // The URL 'pre-suffix' comes from [i+1,k1-1]
											   // Copy "resultURLPreSuffix":
											   n = 0; k2 = i + 1;
											   if (i <= k) { // There's a slash after "host" or "host:port"
												   if (k1 - i > resultURLPreSuffixMaxSize) return False; // there's no room
												   while (k2 <= k1 - 1) resultURLPreSuffix[n++] = reqStr[k2++];
											   }
											   resultURLPreSuffix[n] = '\0';
											   decodeURL(resultURLPreSuffix);

											   i = k + 7; // to go past " RTSP/"
											   parseSucceeded = True;
											   break;
									   }
								   }
								   if (!parseSucceeded) return False;

								   // Look for "CSeq:" (mandatory, case insensitive), skip whitespace,
								   // then read everything up to the next \r or \n as 'CSeq':
								   parseSucceeded = False;
								   for (j = i; (int)j < (int)(reqStrSize-5); ++j) {
									   if (_strncasecmp("CSeq:", &reqStr[j], 5) == 0) {
										   j += 5;
										   while (j < reqStrSize && (reqStr[j] ==  ' ' || reqStr[j] == '\t')) ++j;
										   unsigned n;
										   for (n = 0; n < resultCSeqMaxSize-1 && j < reqStrSize; ++n,++j) {
											   char c = reqStr[j];
											   if (c == '\r' || c == '\n') {
												   parseSucceeded = True;
												   break;
											   }

											   resultCSeq[n] = c;
										   }
										   resultCSeq[n] = '\0';
										   break;
									   }
								   }
								   if (!parseSucceeded) return False;

								   // Look for "Session:" (optional, case insensitive), skip whitespace,
								   // then read everything up to the next \r or \n as 'Session':
								   resultSessionIdStr[0] = '\0'; // default value (empty string)
								   for (j = i; (int)j < (int)(reqStrSize-8); ++j) {
									   if (_strncasecmp("Session:", &reqStr[j], 8) == 0) {
										   j += 8;
										   while (j < reqStrSize && (reqStr[j] ==  ' ' || reqStr[j] == '\t')) ++j;
										   unsigned n;
										   for (n = 0; n < resultSessionIdStrMaxSize-1 && j < reqStrSize; ++n,++j) {
											   char c = reqStr[j];
											   if (c == '\r' || c == '\n') {
												   break;
											   }

											   resultSessionIdStr[n] = c;
										   }
										   resultSessionIdStr[n] = '\0';
										   break;
									   }
								   }

								   // Also: Look for "Content-Length:" (optional, case insensitive)
								   contentLength = 0; // default value
								   for (j = i; (int)j < (int)(reqStrSize-15); ++j) {
									   if (_strncasecmp("Content-Length:", &(reqStr[j]), 15) == 0) {
										   j += 15;
										   while (j < reqStrSize && (reqStr[j] ==  ' ' || reqStr[j] == '\t')) ++j;
										   unsigned num;
										   if (sscanf(&reqStr[j], "%u", &num) == 1) {
											   contentLength = num;
										   }
									   }
								   }
								   return True;
}

Boolean parseRangeParam(char const* paramStr, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime) {
	delete[] absStartTime; delete[] absEndTime;
	absStartTime = absEndTime = NULL; // by default, unless "paramStr" is a "clock=..." string
	double start, end;
	int numCharsMatched = 0;
	Locale l("C", Numeric);
	if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {
		rangeStart = start;
		rangeEnd = end;
	} else if (sscanf(paramStr, "npt = %lf -", &start) == 1) {
		if (start < 0.0) {
			// special case for "npt = -<endtime>", which seems to match here:
			rangeStart = 0.0;
			rangeEnd = -start;
		} else {
			rangeStart = start;
			rangeEnd = 0.0;
		}
	} else if (strcmp(paramStr, "npt=now-") == 0) {
		rangeStart = 0.0;
		rangeEnd = 0.0;
	} else if (sscanf(paramStr, "clock = %n", &numCharsMatched) == 0 && numCharsMatched > 0) {
		rangeStart = rangeEnd = 0.0;

		char const* utcTimes = &paramStr[numCharsMatched];
		size_t len = strlen(utcTimes) + 1;
		char* as = new char[len];
		char* ae = new char[len];
		int sscanfResult = sscanf(utcTimes, "%[^-]-%s", as, ae);
		if (sscanfResult == 2) {
			absStartTime = as;
			absEndTime = ae;
		} else if (sscanfResult == 1) {
			absStartTime = as;
			delete[] ae;
		} else {
			delete[] as; delete[] ae;
			return False;
		}
	} else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched) == 0 && numCharsMatched > 0) {
		// We accept "smtpe=" parameters, but currently do not interpret them.
	} else {
		return False; // The header is malformed
	}

	return True;
}

Boolean parseRangeHeader(char const* buf, double& rangeStart, double& rangeEnd, char*& absStartTime, char*& absEndTime) {
	// First, find "Range:"
	while (1) {
		if (*buf == '\0') return False; // not found
		if (_strncasecmp(buf, "Range: ", 7) == 0) break;
		++buf;
	}

	// Then, run through each of the fields, looking for ones we handle:
	char const* fields = buf + 7;
	while (*fields == ' ') ++fields;
	return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime);
}

// Used to implement "RTSPOptionIsSupported()":
static Boolean isSeparator(char c) { return c == ' ' || c == ',' || c == ';' || c == ':'; }

Boolean RTSPOptionIsSupported(char const* commandName, char const* optionsResponseString) {
	do {
		if (commandName == NULL || optionsResponseString == NULL) break;

		unsigned const commandNameLen = strlen(commandName);
		if (commandNameLen == 0) break;

		// "optionsResponseString" is assumed to be a list of command names, separated by " " and/or ",", ";", or ":"
		// Scan through these, looking for "commandName".
		while (1) {
			// Skip over separators:
			while (*optionsResponseString != '\0' && isSeparator(*optionsResponseString)) ++optionsResponseString;
			if (*optionsResponseString == '\0') break;

			// At this point, "optionsResponseString" begins with a command name (with perhaps a separator afterwads).
			if (strncmp(commandName, optionsResponseString, commandNameLen) == 0) {
				// We have at least a partial match here.
				optionsResponseString += commandNameLen;
				if (*optionsResponseString == '\0' || isSeparator(*optionsResponseString)) return True;
			}

			// No match.  Skip over the rest of the command name:
			while (*optionsResponseString != '\0' && !isSeparator(*optionsResponseString)) ++optionsResponseString;
		}
	} while (0);

	return False;
}

char const* dateHeader() {
	static char buf[200];
#if !defined(_WIN32_WCE)
	time_t tt = time(NULL);
	strftime(buf, sizeof buf, "Date: %a, %b %d %Y %H:%M:%S GMT\r\n", gmtime(&tt));
#else
	// WinCE apparently doesn't have "time()", "strftime()", or "gmtime()",
	// so generate the "Date:" header a different, WinCE-specific way.
	// (Thanks to Pierre l'Hussiez for this code)
	// RSF: But where is the "Date: " string?  This code doesn't look quite right...
	SYSTEMTIME SystemTime;
	GetSystemTime(&SystemTime);
	WCHAR dateFormat[] = L"ddd, MMM dd yyyy";
	WCHAR timeFormat[] = L"HH:mm:ss GMT\r\n";
	WCHAR inBuf[200];
	DWORD locale = LOCALE_NEUTRAL;

	int ret = GetDateFormat(locale, 0, &SystemTime,
		(LPTSTR)dateFormat, (LPTSTR)inBuf, sizeof inBuf);
	inBuf[ret - 1] = ' ';
	ret = GetTimeFormat(locale, 0, &SystemTime,
		(LPTSTR)timeFormat,
		(LPTSTR)inBuf + ret, (sizeof inBuf) - ret);
	wcstombs(buf, inBuf, wcslen(inBuf));
#endif
	return buf;
}

void ignoreSigPipeOnSocket(int socketNum) {
#ifdef USE_SIGNALS
#ifdef SO_NOSIGPIPE
	int set_option = 1;
	setsockopt(socketNum, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof set_option);
#else
	signal(SIGPIPE, SIG_IGN);
#endif
#endif
}
